package esnipe2;

import java.io.*;
import java.net.URL;
//import java.net.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.http.HeaderElement;
import org.apache.http.HttpEntity;
import org.apache.http.HttpMessage;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.Header;
import org.apache.http.impl.client.AbstractHttpClient;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.apache.http.client.CookieStore;
import org.apache.http.cookie.Cookie;


/**
 * @author kcackett
 *
 */

public class EbayAPIImpl implements EbayAPI {
	
	private DefaultHttpClient _client;
	private boolean _loggedIn;
	
	public EbayAPIImpl() throws Exception {
		
		_loggedIn = false;
		_client = new DefaultHttpClient(); 
		_client.setCookieStore(new BasicCookieStore());
		_client.getParams().setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.BROWSER_COMPATIBILITY);
		
	}
	
	/**
	 * Starts a session using userid and pass as authorization credentials
	 */
    @Override
	public boolean startSession(String userid, String pass) {
		/*
		 * Construct a new Http GET request for the login page
		 * After executing this request the server will send
		 * back a series of cookies that it will expect in all
		 * future interactions.  We do not need to deal with 
		 * these cookies directly, simply set the Cookie_Policy
		 * parameter of each request to Browser_Compatibility
		 * and HttpClient will take care of cookies.
		 */
		if (_loggedIn) {
			System.out.println("You are already logged in using this API.  To login under a different name instantiate a new API obj");
			return _loggedIn;
		}
		_loggedIn = false;
		HttpGet httpget = new HttpGet(Constants.EBAY_LOGIN_PAGE);
		httpget.getParams().setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.BROWSER_COMPATIBILITY);
		HttpResponse response;
		try {
			response = _client.execute(httpget);
		} catch (ClientProtocolException e1) {
			e1.printStackTrace();
			return _loggedIn;
		} catch (IOException e1) {
			e1.printStackTrace();
			return _loggedIn;
		}
		
		// In order to reuse the connection we must consume all data from the original connection. 
		this.cleanConnection(response);

		/*
		 * Generate an Http POST request containing all necessary credentials
		 * to login to ebay.  Credentials include all hidden inputs as well
		 * as username and password that are passed into this method.
		 */
		UrlEncodedFormEntity credentity = this.generateCredentials(userid, pass);
		HttpPost httppost = new HttpPost(Constants.EBAY_LOGIN_HANDLER);
		httppost.setEntity(credentity);
		httppost.getParams().setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.BROWSER_COMPATIBILITY);
		try {
			response = _client.execute(httppost);
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			return _loggedIn;
		} catch (IOException e) {
			e.printStackTrace();
			return _loggedIn;
		}
		this.cleanConnection(response);
		this.turnOffJavaScript();
		_loggedIn = true;
		return _loggedIn;
	}
	
	/**
	 * In order to reuse the connection all data from each
	 * request must be consumed.  Call clean connection
	 * before each new request.
	 * @param r
	 * The response we are cleaning
	 */
	private void cleanConnection(HttpResponse r) {
		this.cleanConnection(r.getEntity());
	}
	
	/**
	 * In order to reuse the connection all data from each
	 * request must be consumed.  Call clean connection
	 * before each new request.
	 * @param entity
	 * The entity from response we are cleaning
	 */
	private void cleanConnection(HttpEntity entity) {
		try {
			entity.consumeContent();
		} catch (IOException e) {
			e.printStackTrace();
			return;
		}
	}
	
	private String[] parseCurrentPrice(Element root) {
		LinkedList<Node> q = new LinkedList<Node>();		
		
		String[] toRet = new String[2];
		//Add the children of the first node to the queue
		while (root.hasChildNodes()) q.add(root.removeChild(root.getFirstChild()));
		
		//Walk through the queue and add all hidden inputs as NameValuePairs
		String price = null;
		while (!q.isEmpty()) {
			Node child = q.remove();
			//System.out.println(child.getTextContent());
			if (child.getTextContent().contains(("(Enter US ")) && toRet[1] == null) {
				String text = child.getTextContent();
				int start = text.indexOf("(Enter US $")+"(Enter US $".length();
				int end = text.length();
				text = text.substring(start, end);
				start = 0;
				end = text.indexOf("or");
				toRet[1] = text.substring(start, end);
			}
			if (child.getNodeName().compareTo("span") == 0) {
				//System.out.println(child.getNodeName()+":"+child.getTextContent());
				if (child.hasAttributes()) {
					NamedNodeMap attrMap = child.getAttributes();
					for (int i = 0; i<attrMap.getLength(); i++) {
						Node currAttr = attrMap.item(i);
						//System.out.println(currAttr.getNodeName()+":"+currAttr.getNodeValue());
						if(currAttr.getNodeName().compareTo("itemprop") == 0 && currAttr.getNodeValue().compareTo("price") == 0) toRet[0] = child.getTextContent().substring(4);
					}
				}
			}
			while(child.hasChildNodes()) q.add(child.removeChild(child.getFirstChild()));
		}
		return toRet;
	}
	
    @Override
	public synchronized Pricepair getCurrentPrice(String url) {
		HttpGet httpget = new HttpGet(url);
		httpget.getParams().setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.BROWSER_COMPATIBILITY);
		try {
			HttpEntity entity = _client.execute(httpget).getEntity();
			this.writeFormsTotmp(entity);
			this.cleanConnection(entity);
			Document d = this.parseTmp();
			Element curr = d.getDocumentElement();
			String[] price = this.parseCurrentPrice(curr);
			Pricepair toRet = new Pricepair(Double.parseDouble(price[0]), Double.parseDouble(price[1]), true);
			return toRet;
		} catch (Exception e) {
			return null;
		} 
	}
	
	/**
	 * This is incredibly hacked together and poorly documented.  Do NOT try to modify
	 * unless you know what you are doing. 
	 * @param entity
	 * @param url 
	 * @param val 
	 * @return
	 * @throws IOException
	 */
	private HttpPost generateOfferRequest(HttpEntity entity, double bid, String url) throws IOException {
		
		//Write html to a file first then parse relevant information using the dom	    
		this.writeFormsTotmp(entity);
		
		//Now parse the tmp file
		Document d = this.parseTmp();
		
		List<NameValuePair> formparams = new ArrayList<NameValuePair>();
		System.out.println(Double.toString(bid));
		formparams.add(new BasicNameValuePair("maxbid", Double.toString(bid)));
		Element curr = d.getDocumentElement();
		String action = this.addHiddenInputs(curr, formparams);

				
		//Generate bidding entity from formparams
		UrlEncodedFormEntity credentity;
		try {
			credentity = new UrlEncodedFormEntity(formparams, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		}
		
		System.out.println(action);
		HttpPost httppost = new HttpPost(action);
		httppost.setEntity(credentity);
		httppost.getParams().setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.BROWSER_COMPATIBILITY);
		
		this.addHeaders(httppost, url);
		return httppost;

	}
	
	
	
	private void addHeaders(HttpMessage m, String url) {
		//Add ALL Headers
		m.addHeader("Host", "offer.ebay.com"); //May need to be changed
		m.addHeader("User-Agent","Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.16) Gecko/20111108 Iceweasel/3.5.16 (like Firefox/3.5.16)");
		m.addHeader("Accept","text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
		m.addHeader("Accept-Language","en-us,en;q=0.5");
		//httppost.addHeader("Accept-Encoding","gzip,deflate");
		m.addHeader("Accept-Charset","ISO-8859-1,utf-8;q=0.7,*;q=0.7");
		m.addHeader("Keep-Alive","300");
		m.addHeader("Connection","keep-alive");
		m.addHeader("Referer", url);
		m.addHeader("Content-Type","application/x-www-form-urlencoded");
	}
	
	/**
	 * Closes input lines for an html formatted string
	 * ASSUMES THAT THE INPUT TAG IS NOT CLOSED ALWAYS
	 * (adds closing tags to all opening tags)
	 * @param html
	 * The html we would like to add closing input tags to
	 * @return The html with all input tags closed
	 */
	private String closeInputTags(String html) {
		String ret = "";
		int start = 0;
		int end = html.length();
		while(html.contains("<input")) {
			end = html.indexOf("<input");
			ret = ret + html.substring(start, end);
			html = html.substring(end);
			end = html.indexOf(">")+1;
			ret = ret + html.substring(start, end) + "</input>";
			html = html.substring(end);
		}
		return ret + html;
	}
	
	private String closeTags(String html, String openTag) {
		String ret = "";
		int start = 0;
		int end = html.length();
		while(html.contains("<"+openTag)) {
			end = html.indexOf("<"+openTag);
			ret = ret + html.substring(start, end);
			html = html.substring(end);
			end = html.indexOf(">")+1;
			ret = ret + html.substring(start, end) + "</"+openTag+">";
			html = html.substring(end);
		}
		return ret + html;
	}
	
	private UrlEncodedFormEntity generateCredentials(String userid, String pass) {
		/*
		 * To login to Ebay we must submit our authentication and include all
		 * hidden inputs.  Hidden inputs are stored in a text file and parsed
		 * and added to the credentials at runtime.  The hiddenInput.html
		 * was generated by hand.  DO NOT DELETE IT!
		 */
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder parser;
		
		try {
			parser = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e2) {
			e2.printStackTrace();
			return null;
		}
		File f=new File("hiddenInputFile.html");
		System.out.println("Beginning to parse..");
		Document d;
		try {
			d = parser.parse(new FileInputStream(f));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return null;
		} catch (SAXException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		System.out.println("Finished parsing!");
	
		List<NameValuePair> formparams = new ArrayList<NameValuePair>();
		formparams.add(new BasicNameValuePair("userid", userid));
		formparams.add(new BasicNameValuePair("pass", pass));
		
		LinkedList<Node> q = new LinkedList<Node>();		
		Element curr = d.getDocumentElement();
		
		//Add the children of the first node to the queue
		while (curr.hasChildNodes()) q.add(curr.removeChild(curr.getFirstChild()));
		
		//Walk through the queue and add all hidden inputs as NameValuePairs
		while (!q.isEmpty()) {
			Node child = q.remove();
			if (child.getNodeName().compareTo("input") == 0) {
				if (child.hasAttributes()) {
					NamedNodeMap attrMap = child.getAttributes();
					String name = null;
					String val = null;
					for (int i = 0; i<attrMap.getLength(); i++) {
						Node currAttr = attrMap.item(i);
						if(currAttr.getNodeName().compareTo("name") == 0) name = currAttr.getNodeValue();
						if(currAttr.getNodeName().compareTo("value") == 0) val = currAttr.getNodeValue();
					}
					if (name != null && val != null) formparams.add(new BasicNameValuePair(name, val));
				}
			}
			while(child.hasChildNodes()) q.add(child.removeChild(child.getFirstChild()));
		}
		
		
		//Generate credentials from formparams
		UrlEncodedFormEntity credentity;
		try {
			credentity = new UrlEncodedFormEntity(formparams, "UTF-8");
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
			return null;
		}
		return credentity;
	}
	
	private void turnOffJavaScript() {
		HttpGet httpget = new HttpGet("http://www.ebay.com/sch/i.html?_jsoff=1");
		httpget.getParams().setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.BROWSER_COMPATIBILITY);
		try {
			HttpResponse r = _client.execute(httpget);
			//this.printEntity(r.getEntity());
			this.cleanConnection(r);
		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
	private String addHiddenInputs(Element root, List<NameValuePair> formparams) {
		LinkedList<Node> q = new LinkedList<Node>();		
		
		
		//Add the children of the first node to the queue
		while (root.hasChildNodes()) q.add(root.removeChild(root.getFirstChild()));
		
		//Walk through the queue and add all hidden inputs as NameValuePairs
		String action = null;
		while (!q.isEmpty()) {
			Node child = q.remove();
			if (child.getNodeName().compareTo("form") == 0) {
				if (child.hasAttributes()) {
					//System.out.println(child.getNodeName()+":"+child.getNodeValue());
					NamedNodeMap attrMap = child.getAttributes();
					for (int i = 0; i<attrMap.getLength(); i++) {
						Node currAttr = attrMap.item(i);
						if(currAttr.getNodeName().compareTo("action") == 0) action = currAttr.getNodeValue();
					}
				}
			}
			if (child.getNodeName().compareTo("input") == 0) {
				if (child.hasAttributes()) {
					//System.out.println(child.getNodeName()+":"+child.getNodeValue());
					NamedNodeMap attrMap = child.getAttributes();
					String name = null;
					String val = "";
					boolean button = false;
					for (int i = 0; i<attrMap.getLength(); i++) {
						Node currAttr = attrMap.item(i);
						//System.out.println(currAttr.getNodeName()+":"+currAttr.getNodeValue());
						if(currAttr.getNodeName().compareTo("name") == 0) name = currAttr.getNodeValue();
						if(currAttr.getNodeName().compareTo("value") == 0) val = currAttr.getNodeValue();
						if(currAttr.getNodeValue().compareTo("submit") == 0) button = true;
						if(currAttr.getNodeValue().compareTo("button") == 0) button = true; 
						
					}
					if (name != null && val != null && name.compareTo("maxbid") != 0 && !button) {
						formparams.add(new BasicNameValuePair(name, val));
						//System.out.println(name+":"+val);
					}
				}
			}
			while(child.hasChildNodes()) q.add(child.removeChild(child.getFirstChild()));
		}
		return action;
	}
	
	/**
	 * Test method for printing the content of an entity to console
	 * @param entity 
	 * The entity that we wish to print
	 */
	private void printEntity(HttpEntity entity) {
		//Code for printing the content of the entity is below
		if (entity != null) {
			BufferedWriter out = null;
			try {
				out = new BufferedWriter(new FileWriter("TestResult.html"));
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			
		    InputStream instream = null;
			try {
				instream = entity.getContent();
			} catch (IllegalStateException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}
		    BufferedReader in = new BufferedReader(new InputStreamReader(instream)); 
			String inputLine;
			try {
				while ((inputLine = in.readLine()) != null) {
					//System.out.println(inputLine);
					out.write(inputLine);
				}
				in.close();
				out.flush();
				out.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} 
		
	}
	
	/**
	 * @param filename
	 * Loads the hash of auctions from file "filename" if the program
	 * saves data
	 */
	private void loadAuctions(String filename) {
		
	}
	
	/**
	 * Saves the hashmap of auctions to a file specified in constants
	 */
	private void saveAuctions() {
		
	}
	
	private void writeFormsTotmp(HttpEntity entity) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(entity.getContent()));
		BufferedWriter out = new BufferedWriter(new FileWriter("tmp.html"));
		out.write("<root>");
		String inputLine;
		
		boolean print = false;
		String beginString = "<form";
		String endString = "</form>";
		while ((inputLine = in.readLine()) != null) {	
			do {
				inputLine = inputLine.replaceAll("&nbsp;", "");
				int start = 0;
				int end = inputLine.length();
				if (inputLine.contains(beginString)) {
					start = inputLine.indexOf(beginString);
					print = true;
				}
				if (inputLine.contains(endString)) {
					end = inputLine.indexOf(endString)+endString.length();
					String toWrite = this.closeTags(this.closeInputTags(inputLine.substring(start, end)),"img");
					if (toWrite.contains("action=\"http://offer")) out.write(toWrite);
					print = false;
				}
				if (print) {
					String toWrite = this.closeTags(this.closeInputTags(inputLine.substring(start, end)),"img");
					if (toWrite.contains("action=\"http://offer")) out.write(toWrite);
				}
				inputLine = inputLine.substring(end);
			} while (inputLine.length()>0);
		}
		in.close();
		out.write("</root>");
		out.flush();
		out.close();
	}

	private Document parseTmp() {
		//Instantiate new parse class
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder parser;
		try {
			parser = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e2) {
			e2.printStackTrace();
			return null;
		}
		File f=new File("tmp.html");
		
		//Parse the html file
		System.out.println("Beginning to parse tmp..");
		Document d;
		try {
			d = parser.parse(new FileInputStream(f));
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			return null;
		} catch (SAXException e) {
			e.printStackTrace();
			return null;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
		System.out.println("Finished parsing!");
		
		return d;
	}
	
	@Override
	public synchronized boolean placeBid(String url, double val) {
		if (!_loggedIn) {
			System.out.println("You must be logged in to place bids");
			return false;
		}
		HttpGet httpget = new HttpGet(url);
		httpget.getParams().setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.BROWSER_COMPATIBILITY);
		try {
			HttpEntity entity = _client.execute(httpget).getEntity();
			//_client.getCookieStore().addCookie(new BasicClientCookie("npii","btguid/48df04821340a06437f5ffa5fec1923f50ccf448^cguid/48df076c1340a47a2f967526ff5e346050ccf448^"));
			//_client.getCookieStore().addCookie(new BasicClientCookie("ds2",	"alss/0.xbox%2B3604eed15ed^"));
//			for (Cookie c : _client.getCookieStore().getCookies()) {
//				System.out.println(c.getName()+":"+c.getValue());
//				System.out.println("Domain:" +c.getDomain());
//				System.out.println("Path:" +c.getPath());
//				System.out.println("Expires:" +c.getExpiryDate());
//			}
			HttpPost httppost = this.generateOfferRequest(entity, val, url);
			httppost.getParams().setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.BROWSER_COMPATIBILITY);
			this.cleanConnection(entity);
//			Header[] h = httppost.getAllHeaders();
//			for (int i = 0; i<h.length; i++) System.out.println(h[i].getName()+":"+h[i].getValue());
			//this.printEntity(httppost.getEntity());
			
			HttpResponse response = _client.execute(httppost);
			entity = response.getEntity();
			httppost = this.generateOfferRequest(entity, val, url);
			httppost.getParams().setParameter(ClientPNames.COOKIE_POLICY, org.apache.http.client.params.CookiePolicy.BROWSER_COMPATIBILITY);
			this.cleanConnection(entity);
			
			response = _client.execute(httppost);
			//Header[] h = response.getAllHeaders();
			System.out.println(response.getStatusLine());
			//for (int i = 0; i<h.length; i++) System.out.println(h[i].getName()+":"+h[i].getValue());
			//this.printEntity(response.getEntity());
			
		} catch (ClientProtocolException e) {
			e.printStackTrace();
			return false;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
		
		return true;
	}
	
    @Override
	public boolean closeSession() {
		 _client.getConnectionManager().shutdown();
		return true;
	}

	@Override
	public ItemResult getResult(String url) {
		// TODO Auto-generated method stub
		return ItemResult.RESULT_UNKNOWN;
	}

    @Override
    public boolean getItemInfo(EbayItem item) {
        //throw new UnsupportedOperationException("Not supported yet.");
        item.itemName = "NotYetSupported";
        item.currentPrice = this.getCurrentPrice(item.itemUrl);
        return true;
    }
    
    /**
     * parse string like "Dec 18, 201118:44:45 PST"
     * @param calStr
     * @return 
     */
    public static Calendar parseCal(String calStr) {
        SimpleDateFormat format;
        format = new SimpleDateFormat("MMM dd, yyyykk:mm:ss");
        format.setTimeZone(TimeZone.getTimeZone("PST"));
        Date date = new Date();
        try {
            date = format.parse(calStr.substring(0, 21));
        }
        catch (ParseException e) {
            System.out.println(e.toString());
        }
        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("PST"));
        cal.setTime(date);
        //cal.setTimeZone(TimeZone.getTimeZone(calStr.substring(21, 24)));
        return cal;
    }

    public static String parseCalInv(Calendar cal) {
        SimpleDateFormat format;
        format = new SimpleDateFormat("kk:mm:ss MMM dd, yyyy");
        format.setTimeZone(TimeZone.getTimeZone("PST"));
        Date date = cal.getTime();
        return format.format(date);
    }
	
}

